﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Reflection;
using System.IO;

namespace InstallManager.Msi
{
    public class MsiInstallerSession
    {
        public const MsiInstallLogMode DefaultLogMode = MsiInstallLogMode.FatalExit | MsiInstallLogMode.Error | MsiInstallLogMode.Warning | MsiInstallLogMode.User | MsiInstallLogMode.ActionStart | MsiInstallLogMode.ActionData | MsiInstallLogMode.CommonData | MsiInstallLogMode.PropertyDump | MsiInstallLogMode.Progress | MsiInstallLogMode.ShowDialog | MsiInstallLogMode.ExternalUI;
        public string MsiFile { get; private set; }
        /// <summary>
        /// Creates a new MsiInstallerSession object on the specified msi file.
        /// </summary>
        /// <param name="msiFile">path of the msi file</param>
        /// <exception cref="FileNotFoundException" > if the msiFile is not a valid file path.</exception>
        public MsiInstallerSession(string msiFile)
        {
            if (!File.Exists(msiFile))
            {
                throw new FileNotFoundException(string.Concat(msiFile, " not found!"));
            }
            this.MsiFile = msiFile;
        }

        #region ProductInstalled
        private bool CheckInstalled()
        {
            string upgradeCode = string.Empty;
            using (MsiConnection msiConnection = new MsiConnection(this.MsiFile))
            {
                msiConnection.Open();
                using (MsiCommand msiCommand = new MsiCommand("SELECT Property, Value FROM Property WHERE Property='UpgradeCode'", msiConnection))
                {
                    MsiDataReader msiDataReader = msiCommand.ExecuteReader();
                    using (msiDataReader)
                    {
                        while (msiDataReader.Read())
                        {
                            upgradeCode = msiDataReader.GetString(1);
                        }
                    }
                }
            }
            this.productIds = this.EnumRelatedProducts(upgradeCode);
            return productIds.Count > 0;
        }


        private List<Guid> EnumRelatedProducts(string UpgradeCode)
        {
            unsafe
            {
                List<Guid> guidArray = new List<Guid>();
                uint num = 0;
                StringBuilder stringBuilder = new StringBuilder(38);
                while (MsiInterop.MsiEnumRelatedProducts(UpgradeCode, 0, num, stringBuilder) == 0)
                {
                    guidArray.Add(new Guid(stringBuilder.ToString()));
                    break;
                }
                return guidArray;
            }
        }

        public List<Guid> productIds { get; set; }

        public bool IsInstalled
        {
            get
            {
                if (productIds == null)
                {
                    return CheckInstalled();
                }
                return productIds.Count > 0;
            }
        }
        #endregion

        public event EventHandler<UnhandledExceptionEventArgs> OnSessionException;
        public event EventHandler<InstallCompletedEventArgs> OnInstallCompleted;

        private MsiInstallUIHandler handlerForReference;
        private MsiInstallUIHandler handlerPassed;
        #region public methods
        //use arguments from here: http://msdn.microsoft.com/en-us/library/windows/desktop/aa370905(v=vs.85).aspx
        public void Install(string arguments, MsiInstallLogMode logMode)
        {
            try
            {
                IntPtr zero = IntPtr.Zero;
                MsiInterop.MsiSetInternalUI(MsiInstallUILevel.None, ref zero);
                handlerPassed = OnExternalUI;
                handlerForReference = MsiInterop.MsiSetExternalUI(handlerPassed, logMode,
                    IntPtr.Zero);

                MsiError ret = MsiInterop.MsiInstallProduct(MsiFile, arguments);
                if (OnInstallCompleted != null)
                {
                    OnInstallCompleted(this, new InstallCompletedEventArgs() { ReturnCode = ret });
                }
            }
            catch (Exception exc)
            {
                if (OnSessionException != null)
                {
                    OnSessionException(this, new UnhandledExceptionEventArgs(exc, false));
                }
            }
        }
        public void Install()
        {
            string str = "ADDLOCAL=ALL";
            try
            {
                IntPtr zero = IntPtr.Zero;
                MsiInterop.MsiSetInternalUI(MsiInstallUILevel.None, ref zero);
                handlerPassed = OnExternalUI;
                handlerForReference = MsiInterop.MsiSetExternalUI(handlerPassed, MsiInstallerSession.DefaultLogMode,
                    IntPtr.Zero);

                MsiError ret = MsiInterop.MsiInstallProduct(MsiFile, str);
                if (OnInstallCompleted != null)
                {
                    OnInstallCompleted(this, new InstallCompletedEventArgs() { ReturnCode = ret });
                }
            }
            catch (Exception exc)
            {
                if (OnSessionException != null)
                {
                    OnSessionException(this, new UnhandledExceptionEventArgs(exc, false));
                }
            }
        }

        public void UnInstall()
        {
            string str = "REMOVE=ALL";
            IntPtr zero = IntPtr.Zero;
            try
            {
                MsiInterop.MsiSetInternalUI(MsiInstallUILevel.None, ref zero);
                handlerPassed = OnExternalUI;
                handlerForReference = MsiInterop.MsiSetExternalUI(handlerPassed, MsiInstallerSession.DefaultLogMode,
                    IntPtr.Zero);
                MsiError ret = MsiInterop.MsiInstallProduct(MsiFile, str);
                if (OnInstallCompleted != null)
                {
                    OnInstallCompleted(this, new InstallCompletedEventArgs() { ReturnCode = ret });
                }
            }
            catch (Exception exc)
            {
                if (OnSessionException != null)
                {
                    OnSessionException(this, new UnhandledExceptionEventArgs(exc, false));
                }
            }
        }

        #endregion

        #region events
        public event EventHandler<MsiMessageEventArgs> OnMsiMessage;
        public event EventHandler<MsiMessageEventArgs> OnShowDialogMessage;
        public event EventHandler<MsiMessageEventArgs> OnInitializeMessage;
        public event EventHandler<MsiMessageEventArgs> OnCommonDataMessage;
        public event EventHandler<MsiMessageEventArgs> OnProgressMessage;
        public event EventHandler<MsiMessageEventArgs> OnActionDataMessage;
        public event EventHandler<MsiMessageEventArgs> OnActionStartMessage;
        public event EventHandler<MsiMessageEventArgs> OnOutOfDiskSpaceMessage;
        public event EventHandler<MsiMessageEventArgs> OnFilesInUseMessage;
        public event EventHandler<MsiMessageEventArgs> OnInfoMessage;
        public event EventHandler<MsiMessageEventArgs> OnUserMessage;
        public event EventHandler<MsiMessageEventArgs> OnWarningMessage;
        public event EventHandler<MsiMessageEventArgs> OnErrorMessage;
        public event EventHandler<MsiMessageEventArgs> OnFatalErrorMessage;
        public event EventHandler<MsiMessageEventArgs> OnTerminateMessage;

        internal int OnExternalUI(IntPtr context, uint messageType, string message)
        {
            if (String.IsNullOrWhiteSpace(message))
                return 0;




            MsiInstallMessage mt = (MsiInstallMessage)(0xFF000000 & (uint)messageType);
            MsiUIFlags uiFlags = (MsiUIFlags)(0x00FFFFFF & (uint)messageType);
            var eventargs = new MsiMessageEventArgs()
            {
                MessageType = mt,
                Message = message,
                UIFlags = uiFlags
            };
            if (OnMsiMessage != null)
            {
                OnMsiMessage(this, eventargs);
            }
            switch (mt)
            {
                //Premature termination
                case MsiInstallMessage.FatalExit:
                    {
                        if (OnFatalErrorMessage != null)
                        {
                            OnFatalErrorMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }

                case MsiInstallMessage.Error:
                    {
                        if (OnErrorMessage != null)
                        {
                            OnErrorMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }
                case MsiInstallMessage.Warning:
                    {
                        if (OnWarningMessage != null)
                        {
                            OnWarningMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }
                case MsiInstallMessage.User:
                    {
                        if (OnUserMessage != null)
                        {
                            OnUserMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }

                case MsiInstallMessage.Info:
                    {
                        if (OnInfoMessage != null)
                        {
                            OnInfoMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }

                case MsiInstallMessage.FilesInUse:
                    {
                        if (OnFilesInUseMessage != null)
                        {
                            OnFilesInUseMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }

                case MsiInstallMessage.ResolveSource:
                    return 0;

                case MsiInstallMessage.OutOfDiskSpace:
                    {
                        if (OnOutOfDiskSpaceMessage != null)
                        {
                            OnOutOfDiskSpaceMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }

                case MsiInstallMessage.ActionStart:
                    {
                        if (OnActionStartMessage != null)
                        {
                            OnActionStartMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }
                case MsiInstallMessage.ActionData:
                    {
                        if (OnActionDataMessage != null)
                        {
                            OnActionDataMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }
                case MsiInstallMessage.Progress:
                    {
                        if (OnProgressMessage != null)
                        {
                            OnProgressMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }
                case MsiInstallMessage.CommonData:
                    {
                        if (OnCommonDataMessage != null)
                        {
                            OnCommonDataMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }

                case MsiInstallMessage.Initialize:
                    {
                        if (OnInitializeMessage != null)
                        {
                            OnInitializeMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }

                case MsiInstallMessage.Terminate:
                    {

                        if (OnTerminateMessage != null)
                        {
                            OnTerminateMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }

                case MsiInstallMessage.ShowDialog:
                    {
                        if (OnShowDialogMessage != null)
                        {
                            OnShowDialogMessage(this, eventargs);
                            return (int)eventargs.ReturnValue;
                        }
                        return (int)MsiUIReturnValues.Ignored;
                    }

                default:
                    return 0;
            }
        }


        #endregion


    }
}
